<?php

namespace app\common\library;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Message;
use GuzzleHttp\RequestOptions;

class MyHttp {

    private static $instance;

    private $client;
    private $responseBodyContents;
    private $exception;

    private $config = [
        'timeout' => null,
        'http_errors' => null,
        'expect' => null,
        'headers' => [],
    ];

    private function __construct($config = [])
    {
        $this->config['timeout'] = isset($config['timeout']) ? $config['timeout'] : 10;//超时时间，单位为秒
        $this->config['http_errors'] = isset($config['http_errors']) ? $config['http_errors'] : false;//当返回非200时，如果为true，就会抛异常
        $this->config['expect'] = isset($config['expect']) ? $config['expect'] : false;//设为true或者，content-length大于等于设定的值，header会加上expect:100-Continue

        //格式如：['headers'=>['my-name'=>'test']]。另外无论设置如何，最后都会自动添加一个"host"和"user-agent"，而user-agent可被覆盖。
        isset($config['headers']) and $this->config['headers'] = $config['headers'];
        isset($config['header']) and $this->config['headers'] += $config['header'];

        $this->client = new Client($this->config);
    }

    public static function instance ($config = [])
    {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }


    public function get($uri, $data = [], $options = [])
    {
        $data and $options[RequestOptions::QUERY] = $data;
        $this->request('GET', $uri, $options);
        return $this->getResponseBodyContents();
    }

    public function postJson($uri, $data = null, $options = [])
    {
        $options[RequestOptions::JSON] = $data;
        $this->request('POST', $uri, $options);
        return $this->getResponseBodyContents();
    }

    public function postForm($uri, $data = null, $options = [])
    {
        $options[RequestOptions::FORM_PARAMS] = $data;
        $this->request('POST', $uri, $options);
        return $this->getResponseBodyContents();
    }

    public function request($method,$uri, $options = [])
    {
        $this->clear();
        isset($options['timeout']) and $options[RequestOptions::TIMEOUT] = $options['timeout'];
        isset($options['http_errors']) and $options[RequestOptions::HTTP_ERRORS] = $options['http_errors'];
        isset($options['expect']) and $options[RequestOptions::EXPECT] = $options['expect'];
        isset($options['headers']) and $options[RequestOptions::HEADERS] = $options['headers'];
        isset($options['header']) and $options[RequestOptions::HEADERS] += $options['header'];

        try {
            $response = $this->client->request($method, $uri, $options);
            $this->responseBodyContents = $response->getBody()->getContents();//注意，getContents只能获取一次内容
            return true;

        } catch (GuzzleException $e) {
            if (!$this->config['http_errors'] && $e instanceof RequestException && $e->hasResponse()) {
                //有些API虽然HTTP CODE不是200，但仍然会返回内容
                $this->responseBodyContents = $e->getResponse()->getBody()->getContents();
                return true;
            }

            if ($this->config['http_errors']) {
                $this->exception = $e;
            }

            $data = [
                'trace' => $e->getTraceAsString(),
                'options' => $options,
            ];
            if ($e instanceof RequestException) {
                $data['request'] = Message::toString($e->getRequest());
                $data['response'] = Message::toString($e->getResponse());
            } elseif ($e instanceof ConnectException) {
                $data['request'] = Message::toString($e->getRequest());
            }

            add_error_log(__METHOD__ . $e->getMessage(), $data);
            return null;
        }
    }

    public function getResponseBodyContents()
    {
        return $this->responseBodyContents;
    }

    /**
     * @return GuzzleException
     */
    public function getException()
    {
        return $this->exception;
    }

    public function clear()
    {
        $this->exception = null;
        $this->responseBodyContents = null;
    }

    public function __destruct()
    {
        $this->clear();
    }

}
